/**
 * Copyright (c) 2014-2016, Data Geekery GmbH, contact@datageekery.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.unbiz.either;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import com.unbiz.tuple.Tuple2;
import com.unbiz.tuple.Tuple3;

/**
 * A range[low,high) is a special {@link Tuple2} with two times the same type.
 * if T is Integer, this is int range.
 * 
 * @author Lukas Eder
 */
public class Range<T extends Comparable> extends Tuple3<T, T, T> {

    private static final long serialVersionUID = 1L;
    
    public static <T extends Comparable<T>> Range<T> of(T _0, T _1) {
    	return _0.compareTo(_1) <= 0 ? new Range<T>(_0, _1): new Range<T>(_1, _0);       
    }
    
    /**
     * Range.of(10,-10,-2)
     * @param _0
     * @param _1
     * @param step
     * @return
     */
    public static <T extends Comparable<T>> Range<T> of(T _0, T _1, T nstep) {
    	Number step = (Number) nstep;
    	if(step.intValue()>0){
    		return _0.compareTo(_1) <= 0 ? new Range<T>(_0, _1, nstep): new Range<T>(_1, _0, nstep);       
    	}
    	else if(step.intValue()<0){
    		return _0.compareTo(_1) >= 0 ? new Range<T>(_0, _1, nstep): new Range<T>(_1, _0, nstep);     
    	}
    	else{
    		throw new IllegalArgumentException("step must is not 0");
    	}
    }

    protected Range(T t1, T t2) {
    	super(t1, t2, null);
    }
    
    protected Range(T t1,  T t2, T step) {
    	super(t1, t2, step);
    }

    protected Range(Tuple3<T, T, T> tuple) {
        this(tuple._0, tuple._1,tuple._2);
    }
    
    public Number step() {
    	if(_2==null) return 1;    	
        return (Number)_2;
    }
    
    public int size() {
    	if(_0 instanceof Integer && _1 instanceof Integer){
    		Integer l = (Integer)_0;
    		Integer h = (Integer)_1;
    		return (int)(h.intValue() - l.intValue())/step().intValue();
    	}
    	if(_0 instanceof Number && _1 instanceof Number){
    		Number l = (Number)_0;
    		Number h = (Number)_1;
    		return (int)((h.doubleValue() - l.doubleValue())/step().doubleValue());
    	}
        return super.size();
    }

    @Override
    public Object apply(int value) {
    	if(value>=size()){
    		throw new IndexOutOfBoundsException();
    	}
    	if(_0 instanceof Integer && _1 instanceof Integer){
    		Integer l = (Integer)_0;
    		Integer h = (Integer)_1;
    		int v = l.intValue() + value*step().intValue();    		
    		return v;
    	}
    	if(_0 instanceof Number && _1 instanceof Number){
    		Number l = (Number)_0;
    		Number h = (Number)_1;
    		double v = l.doubleValue() + value*step().doubleValue();    		
    		return numberOf(v);
    	}
        return super.apply(value);
    }
   

    /**
     * Whether two ranges overlap.
     * <p>
     * <code><pre>
     * // true
     * range(1, 3).overlaps(range(2, 4))
     *
     * // false
     * range(1, 3).overlaps(range(5, 8))
     * </pre></code>
     */
    public boolean overlaps(Range<T> right) {
        return Range.overlaps(this, right);
    }

    /**
     * Whether two ranges overlap. 重叠
     * <p>
     * <code><pre>
     * // true
     * range(1, 3).overlaps(2, 4)
     *
     * // false
     * range(1, 3).overlaps(5, 8)
     * </pre></code>
     */
    public boolean overlaps(T t1, T t2) {
        return overlaps(Range.of(t1, t2));
    }
    
    public static final <T extends Comparable<? super T>> boolean overlaps(Range<T> left, Range<T> right) {
        return left._0.compareTo(right._1) <= 0
            && left._1.compareTo(right._0) >= 0;
    }
    /**
     * The intersection of two ranges. 交集
     * <p>
     * <code><pre>
     * // (2, 3)
     * range(1, 3).intersect(range(2, 4))
     *
     * // none
     * range(1, 3).intersect(range(5, 8))
     * </pre></code>
     */
    public static final <T extends Comparable<? super T>> Optional<Range<T>> intersect(Range<T> left, Range<T> right) {
        if (overlaps(left, right))
            return Optional.of(new Range<T>(
                left._0.compareTo(right._0) >= 0 ? left._0 : right._0,
                left._1.compareTo(right._1) <= 0 ? left._1 : right._1
            ));
        else
            return Optional.empty();
    }

    /**
     * The intersection of two ranges.
     * <p>
     * <code><pre>
     * // (2, 3)
     * range(1, 3).intersect(range(2, 4))
     *
     * // none
     * range(1, 3).intersect(range(5, 8))
     * </pre></code>
     */
    public Optional<Range<T>> intersect(Range<T> other) {
        return Range.intersect(this, other);
    }

    /**
     * The intersection of two ranges.
     * <p>
     * <code><pre>
     * // (2, 3)
     * range(1, 3).intersect(2, 4)
     *
     * // none
     * range(1, 3).intersect(5, 8)
     * </pre></code>
     */
    public Optional<Range<T>> intersect(T t1, T t2) {
        return intersect(Range.of(t1, t2));
    }
  
    public final List<?> toList() {
    	if(_0 instanceof Integer && _1 instanceof Integer){
    		Integer l = (Integer)_0;
    		Integer h = (Integer)_1;
    		int size = size();
    		int step = step().intValue();
    		ArrayList<Integer> list = new ArrayList<Integer>(size);
    		int v = l.intValue();
    		for(int i=0;i<size;i++){
    			list.add(v+step*i);
    		}
    		return list;
    	}
    	if(_0 instanceof Number && _1 instanceof Number){
    		Number l = (Number)_0;
    		Number h = (Number)_1;
    		int size = size();
    		double step = step().doubleValue();
    		ArrayList<Number> list = new ArrayList<Number>(size);
    		double v = l.doubleValue();
    		
    		for(int i=0;i<size;i++){
    			list.add(numberOf(v+step*i));
    		}
    		return list;
    	}
        return Arrays.asList(toArray());
    }
    
    //return double int long
    public Number  numberOf(double v){
    	if(v>Long.MAX_VALUE || v<Long.MIN_VALUE){
    		return new Double(v);
    	}
    	long l = (long) v;
    	if(v==l){
    		if(l>Integer.MAX_VALUE || l<Integer.MIN_VALUE){
        		return Long.valueOf(l);
        	}
    		return Integer.valueOf((int)v);
    	}    	
    	return new Double(v);
    }
}
